package main

import (
    "fmt"
    "math/rand"
    "time"
)

func main() {
    rand.Seed(time.Now().UnixNano())
    const genes = " abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ!."
    target := "Not all those who wander are lost."
    //target := "zzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzzz"
    calc := func (current string) int {
        return calculate(target, current)
    }

    start := time.Now()

    disp := func (current string, value int) {
        fmt.Print(current)
        fmt.Print("\t")
        fmt.Print(value)
        fmt.Print("\t")
        fmt.Println(time.Since(start))
    }

    var best, try = getBest(calc, disp, genes, len(target))
    fmt.Println(best)
    fmt.Print("Total time: ")
    fmt.Println(time.Since(start))
    fmt.Println("Tried: ", try)
}

func getBest(calculate func(string) int, disp func(string, int),
genes string, length int) (string, int) {
    var bestGenes = generateParent(genes, length)
    value := calculate(bestGenes)
    var bestValue = value
    var try = 0

    for bestValue < length {
        try++

        current := mutateParent(bestGenes, genes)
        value := calculate(current)
        if value > bestValue {
            disp(current, value)
            fmt.Println("Try: ", try)
            try = 0
            bestValue = value
            bestGenes = current
        }
    }

    return bestGenes,try
}

func mutateParent(parent, genes string) string {
    geneIndex := rand.Intn(len(genes))
    parentIndex := rand.Intn(len(parent))
    current := ""
    if parentIndex > 0 {
        current += parent[:parentIndex]
    }
    current += genes[geneIndex:1+geneIndex]
    if parentIndex+1 < len(parent) {
        current += parent[parentIndex+1:]
    }
    return current
}

func generateParent(genes string, length int) string {
    s := ""
    for i := 0; i < length; i++ {
        index := rand.Intn(len(genes))
        s += genes[index:1+index]
    }
    return s
}

func calculate(target, current string) int {
    sameCount := 0
    for i := 0; i < len(target); i++ {
        if target[i] == current[i] {
            sameCount++
        }
    }
    return sameCount
}
